﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.Threading;
using IrrlichtLime.Core;

namespace BillardPHS4700 {

    /// <summary>
    /// This is top-level game manager, responsible for driving all the others
    /// and maintaining the global game state
    /// </summary>
    class GameState {

        Simulation m_simulation;
        Engine m_engine;
        Interface m_interface;
        bool m_shotIsOnGoing;
        const float UPDATES_PER_SECOND = 30f;

        public GameState() {
            m_engine = new Engine();
            m_engine.Initialize();
            m_simulation = new Simulation(m_engine) {
                TimeStep = 1f / UPDATES_PER_SECOND
            };
            m_simulation.ShotFinished += OnShotFinished;
            m_interface = new Interface(m_engine, m_simulation);
            m_interface.Shoot += OnShoot;
            m_interface.SwitchMode(ControlType.PlacingMode);
        }

        void OnShotFinished(object sender, EventArgs eventArgs) {
            m_interface.SwitchMode(ControlType.PlacingMode);
            m_shotIsOnGoing = false;
        }

        void OnShoot(object sender, EventArgs eventArgs) {
            m_engine.ActiveCamera = CameraType.TopDown;
            m_shotIsOnGoing = true;
            m_interface.SwitchMode(ControlType.SimulationMode);
            m_simulation.Shoot();
        }

        /// <summary>
        /// Runs the game. This method doesn't return until the game has been closed.
        /// </summary>
        public void Run() {
            // This is the game update/draw loop, using a fixed time step
            var period = 1000f / UPDATES_PER_SECOND;

            var stopWatch = Stopwatch.StartNew();
            while (m_engine.Update()) {
                if (m_shotIsOnGoing) {
                    m_simulation.Update();
                }
                for (int i = 0; i < Simulation.NUM_BALLS; ++i) {
                    UpdateBallShadow(i);
                }
                m_engine.Draw();

                // If there is time left, wait it out
                // This is not a very accurate method but it's simple and CPU-friendly
                while (stopWatch.ElapsedMilliseconds < period) {
                    Thread.Sleep(1);
                }

                stopWatch.Restart();
            }
        }



        void UpdateBallShadow(int i) {

            var ballPos = m_simulation.GetBallPosition(i);
            var lightPos = new Vector3Df(0f, 0f, Constants.LightHeight);

            var tablePlane = new Plane3Df(0f, 0f, Constants.TableHeight, 0f, 0f, 1f);
            Vector3Df shadowCenter;
            tablePlane.GetIntersectionWithLine(lightPos, ballPos - lightPos, out shadowCenter);
            shadowCenter.Z += 0.2f;


            var ballCenterVector = ballPos - new Vector3Df(0, 0, Constants.TableHeight);
            var ballCenter2D = new Vector2Df(ballCenterVector.X, ballCenterVector.Y);
            var distSQ = ballCenterVector.LengthSQ;
            var maxSQ = new Vector3Df(Constants.LeftWallX, Constants.BottomWallY, Constants.LightHeight - 100 * Constants.TableHeight).LengthSQ;

            m_engine.Shadows[i].Position = shadowCenter;
            m_engine.Shadows[i].Scale = new Vector3Df(1.2f + (distSQ / (maxSQ * 1.1f)), 1f, 1.2f);
            m_engine.Shadows[i].Rotation = new Vector3Df(90f, 0f, (float)-ballCenter2D.Angle);
        }

    }
}
